package top.gokzzz.jpa.utils;

import top.gokzzz.jpa.annotation.Query;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * JPA 通用查詢工具類
 *
 * @author Gok
 * @version 1.1
 */
@SuppressWarnings({"unchecked", "all"})
public class QueryUtil {
    public static <R, Q> Predicate getPredicate(Root<R> root, Q query, CriteriaBuilder cb) {
        List<Predicate> predicates = new ArrayList<>();

        Field[] fields = query.getClass().getDeclaredFields();
        for (Field field : fields) {
            // 檢查字段上是否有 @Query 註解
            if (field.isAnnotationPresent(Query.class)) {
                Query queryAnnotation = field.getAnnotation(Query.class);
                // 獲取字段路徑
                String fieldPath = getFieldPath(field, queryAnnotation);

                // 設置字段可訪問
                field.setAccessible(true);
                try {
                    Object value = field.get(query);
                    if (value != null) {
                        // 解析嵌套路徑
                        Path<?> path = resolveNestedPath(root, fieldPath);
                        Class<?> fieldType = field.getType();
                        switch (queryAnnotation.type()) {
                            case EQUAL:
                                predicates.add(cb.equal(path, value));
                                break;
                            case NOT_EQUAL:
                                predicates.add(cb.notEqual(path, value));
                                break;
                            case LIKE:
                                predicates.add(cb.like(path.as(String.class), "%" + value + "%"));
                                break;
                            case GREATER_THAN:
                                predicates.add(cb.greaterThanOrEqualTo(path.as((Class<? extends Comparable>) fieldType), (Comparable) value));
                                break;
                            case LESS_THAN:
                                predicates.add(cb.lessThanOrEqualTo(path.as((Class<? extends Comparable>) fieldType), (Comparable) value));
                                break;
                            case IN:
                                if (value instanceof Iterable) {
                                    List<Object> values = StreamSupport.stream(((Iterable<?>) value).spliterator(), false).collect(Collectors.toList());
                                    predicates.add(path.in(values));
                                } else if (value.getClass().isArray()) {
                                    Object[] array = (Object[]) value;
                                    predicates.add(path.in(Arrays.asList(array)));
                                } else {
                                    throw new IllegalArgumentException("Value for IN query must be an instance of Iterable");
                                }
                                break;
                            case NOT_IN:
                                if (value instanceof Iterable) {
                                    List<Object> values = StreamSupport.stream(((Iterable<?>) value).spliterator(), false).collect(Collectors.toList());
                                    predicates.add(cb.not(path.in(values)));
                                } else if (value.getClass().isArray()) {
                                    Object[] array = (Object[]) value;
                                    predicates.add(cb.not(path.in(Arrays.asList(array))));
                                } else {
                                    throw new IllegalArgumentException("Value for NOT IN query must be an instance of Iterable");
                                }
                                break;
                            case IS_NULL:
                                if (value instanceof Boolean && value == Boolean.TRUE) {
                                    predicates.add(cb.isNull(path));
                                    break;
                                }
                            case IS_NOT_NULL:
                                if (value instanceof Boolean && value == Boolean.TRUE) {
                                    predicates.add(cb.isNotNull(path));
                                    break;
                                }
                                break;
                        }
                    }
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        return cb.and(predicates.toArray(new Predicate[0]));
    }

    /**
     * 獲取字段路徑
     *
     * @param field      字段
     * @param annotation 註解
     * @return {@link String }
     */
    private static String getFieldPath(Field field, Query annotation) {
        return annotation.path().isEmpty() ? field.getName() : annotation.path();
    }

    /**
     * 解析嵌套路徑
     *
     * @param root      root
     * @param fieldPath 嵌套字段路徑
     * @return {@link Path }<{@link ? }>
     */
    private static Path<?> resolveNestedPath(Root<?> root, String fieldPath) {
        String[] pathSegments = fieldPath.split("\\.");
        Path<?> path = root;
        for (String segment : pathSegments) {
            path = path.get(segment);
        }
        return path;
    }
}
